import json
import time
import psutil
import socket
from queue import Queue
from threading import Thread, Lock
from websockets.sync import client

from src.base.datatype import RobotEvent
from src.conf.robot_conf import RobotConf
from src.conf.conf_manager import ConfManager
from src.logger import Logger


class Handler:
    def __init__(self, conf: ConfManager, logger: Logger):
        """处理与机器人的ws连接"""
        self.__logger = logger
        self.__ini: RobotConf = conf.robot_conf
        # 拼接ws连接url
        self.__url = f"ws://{self.__ini.host}:{self.__ini.port}{self.__ini.node}" \
                     f"?access_token={self.__ini.token}"
        # 内部参数
        self.bot_qq = None
        self.__task = Queue()
        self.__recv = Queue()
        self.__api_lock = Lock()
        self.__check_lock = Lock()
        self.alive = False
        self.event = Queue()
        self.__failed_times = 0
        self.__thread_list: list = []
        self.connection = None

    def do_cmd(self, action: str, params: dict = None, echo="") -> dict:
        """提供给程序进行api交互"""
        if params is None:
            params = {}
        task = json.dumps({"action": action, "params": params, "echo": echo})
        with self.__api_lock:
            self.__task.put(task)
            res: dict = self.__recv.get(timeout=5)
        return res

    def __safe_connect(self):
        """安全的连接ws"""
        try:
            connection = client.connect(self.__url)
            res: dict = json.loads(connection.recv())
            if res.get("status") == "failed":
                raise Exception(res.get("message"))
            self.alive = True
            if self.__failed_times == 0:
                self.__logger.add_log(f"[DLS] 机器人连接成功！")
            else:
                self.__failed_times = 0
                self.__logger.add_log(f"[DLS] 机器人重新连接成功！")
        except Exception as e:
            connection = None
            self.alive = False
            if self.__failed_times < 2:
                self.__failed_times += 1
                self.__logger.add_log(f"[DLS] 机器人连接失败: {e}")
        return connection

    def __auto_reconnect(self):
        """自动重连ws"""
        while True:
            time.sleep(3)
            with self.__check_lock:
                # 为none证明连接失败，直接进行重连
                if self.connection is None:
                    self.connection = self.__safe_connect()
                    continue
                # 首先检测websocket绑定的网卡网线是否插好
                if not self.__check_cable():
                    self.connection.close()
                    self.__logger.add_log("[DLS] 网线被拔出！")
                    continue
                # 确认自己网卡正常后用tcp连接检测对方网卡是否正常
                if not self.__check_port_is_up():
                    # 丢一次包后开始快速检测端口计算丢包率
                    pack_loss = self.__check_port_pack_loss()
                    if pack_loss > 0.4:
                        self.connection.close()
                        self.__logger.add_log(f"[DLS] 当前丢包严重（丢包率高达{pack_loss*100}%），机器人框架可能已经掉线，准备尝试重新连接")
                        continue

    def __check_cable(self) -> bool:
        """检测websocket绑定的网卡网线是否插好"""
        # 主要检测网线松动破损、人为拔线、路由器或交换机故障或重启等导致网卡掉线情况
        current_ip = self.connection.socket.getsockname()
        # 获取所有网络接口的地址信息
        interfaces = psutil.net_if_addrs()
        # 遍历所有接口的地址
        for interface_name, addresses in interfaces.items():
            for addr in addresses:
                if addr.address == current_ip[0]:  # addr.family == socket.AF_INET and
                    for iface, status in psutil.net_if_stats().items():
                        if interface_name == iface:
                            if not status.isup:
                                # 如果某个网口没插网线，那么自动绑定就不会绑定他
                                return False
        return True

    def __check_port_pack_loss(self) -> float:
        """检查到目标端口的丢包率"""
        result: list[bool] = []
        for i in range(1, 10):
            time.sleep(0.1)
            result.append(self.__check_port_is_up())
        return result.count(True) / len(result)

    def __check_port_is_up(self) -> bool:
        """创建tcp连接查看对方服务端是否在线"""
        try:
            with socket.create_connection((self.__ini.host, self.__ini.port), timeout=5):
                return True
        except (socket.timeout, ConnectionRefusedError):
            return False

    def __recv_event(self) -> None:
        """接收来自机器人的通知"""
        while True:
            if self.connection is None:
                time.sleep(0.5)
                continue
            try:
                res: dict = json.loads(self.connection.recv())
                if "post_type" not in res:
                    self.__recv.put(res)
                else:
                    try:
                        self.event.put(RobotEvent(res))
                    except Exception as e:
                        self.__logger.add_log(f"[DLS] 实例化异常: {e} 消息文本: {res}")
            except Exception as e:
                with self.__check_lock:
                    self.connection = None
                self.__logger.add_log(f"[DLS] 机器人连接中断: {type(e)}")
                # 解除任务进程阻塞
                self.__task.put("")

    def __communicate(self) -> None:
        """发送指令与获取返回结果"""
        while True:
            if self.connection is None:
                time.sleep(0.5)
                continue
            try:
                self.connection.send(self.__task.get())
            except Exception:
                pass

    def __init_thread(self, func) -> None:
        th = Thread(target=func, daemon=True)
        self.__thread_list.append(th)
        th.start()

    # 初始化机器人QQ号
    def __init_info(self) -> None:
        try:
            back = self.do_cmd("get_login_info")
            self.bot_qq = back["data"]["user_id"]
        except Exception:
            pass

    def start(self) -> None:
        self.__init_thread(self.__communicate)
        self.__init_thread(self.__recv_event)
        self.__init_thread(self.__auto_reconnect)
        self.__init_info()

    def reload(self) -> None:
        self.__url = f"ws://{self.__ini.host}:{self.__ini.port}{self.__ini.node}" \
                     f"?access_token={self.__ini.token}"
